package dao

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"gin-gateway/core/init"
	"gin-gateway/core/utils"
	"gin-gateway/reverse_proxy/load_balance"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"gorm.io/gorm"
)

type LoadBalance struct {
	ID            int64  `json:"id" gorm:"primary_key"`
	ServiceID     int64  `json:"service_id" gorm:"column:service_id" description:"服务id	"`
	CheckMethod   int    `json:"check_method" gorm:"column:check_method" description:"检查方法 tcpchk=检测端口是否握手成功"`
	CheckTimeout  int    `json:"check_timeout" gorm:"column:check_timeout" description:"check超时时间	"`
	CheckInterval int    `json:"check_interval" gorm:"column:check_interval" description:"检查间隔, 单位s		"`
	RoundType     int    `json:"round_type" gorm:"column:round_type" description:"轮询方式 round/weight_round/random/ip_hash"`
	IpList        string `json:"ip_list" gorm:"column:ip_list" description:"ip列表，ip:port"`
	WeightList    string `json:"weight_list" gorm:"column:weight_list" description:"权重列表"`
	ForbidList    string `json:"forbid_list" gorm:"column:forbid_list" description:"禁用ip列表"`

	UpstreamConnectTimeout int `json:"upstream_connect_timeout" gorm:"column:upstream_connect_timeout" description:"下游建立连接超时, 单位s"`
	UpstreamHeaderTimeout  int `json:"upstream_header_timeout" gorm:"column:upstream_header_timeout" description:"下游获取header超时, 单位s	"`
	UpstreamIdleTimeout    int `json:"upstream_idle_timeout" gorm:"column:upstream_idle_timeout" description:"下游链接最大空闲时间, 单位s	"`
	UpstreamMaxIdle        int `json:"upstream_max_idle" gorm:"column:upstream_max_idle" description:"下游最大空闲链接数"`

	Resolver int `json:"resolver" gorm:"column:resolver" description:"服务发现方式,0数据库存放 ，1etcd"`
}

func (l *LoadBalance) TableName() string {
	return "gateway_service_load_balance"
}

func (l *LoadBalance) Find(c *gin.Context, tx *gorm.DB, search *LoadBalance) (*LoadBalance, error) {
	model := &LoadBalance{}
	err := tx.WithContext(Init.NewTraceContext(c)).Where(search).Find(model).Error
	return model, err
}

func (l *LoadBalance) Save(c *gin.Context, tx *gorm.DB) error {
	if err := tx.WithContext(Init.NewTraceContext(c)).Save(l).Error; err != nil {
		return err
	}
	return nil
}

func (l *LoadBalance) GetIPListByModel() []string {
	return strings.Split(l.IpList, ",")
}

func (l *LoadBalance) GetWeightListByModel() []string {
	return strings.Split(l.WeightList, ",")
}

var LoadBalancerManager *LoadBalancer // 负载均衡管理器

// 负载均衡管理器
type LoadBalancer struct {
	LoadBalanceMap      map[string]*LoadBalancerItem // k= ServiceName
	LoadBalanceItemList []*LoadBalancerItem
	Locker              sync.RWMutex
}

type LoadBalancerItem struct {
	LoadBalance load_balance.LoadBalancer
	ServiceName string
}

func NewLoadBalancer() *LoadBalancer {
	return &LoadBalancer{
		LoadBalanceMap:      map[string]*LoadBalancerItem{},
		LoadBalanceItemList: []*LoadBalancerItem{},
		Locker:              sync.RWMutex{},
	}
}

func init() {
	LoadBalancerManager = NewLoadBalancer()
}

// 你可能会问，第一次读锁检查没找到，为什么升级写锁后还要再检查一次？
//这是因为： 在第一次读锁释放后，写锁加之前，可能有其他协程已经创建了对应的 lbrItem 并写入了 LoadBalanceMap。
//如果不再检查，可能会重复创建同一个 ServiceName 的 lbrItem，导致数据不一致或资源浪费。
// GetLoadBalancer 获取负载均衡器
func (lbr *LoadBalancer) GetLoadBalancer(s *ServiceDetail) (load_balance.LoadBalancer, error) {
	lbr.Locker.RLock() // 读锁查找
	if lbrItem, ok := lbr.LoadBalanceMap[s.Info.ServiceName]; ok {
		lbr.Locker.RUnlock()
		return lbrItem.LoadBalance, nil
	}
	lbr.Locker.RUnlock()

	lbr.Locker.Lock() // 写锁写入
	defer lbr.Locker.Unlock()
	// 再次检查
	if lbrItem, ok := lbr.LoadBalanceMap[s.Info.ServiceName]; ok { // 其他协程已经写入，则找到数据返回
		return lbrItem.LoadBalance, nil
	}

	lbCheckConf, err := GetLoadBalancerCheckConf(s)
	if err != nil {
		return nil, err
	}

	lb := load_balance.LoadBanlanceFactoryWithConf(load_balance.LbType(s.LoadBalance.RoundType), lbCheckConf)
	lbItem := &LoadBalancerItem{ //save to map and slice
		LoadBalance: lb,
		ServiceName: s.Info.ServiceName,
	}
	lbr.LoadBalanceItemList = append(lbr.LoadBalanceItemList, lbItem)
	lbr.LoadBalanceMap[s.Info.ServiceName] = lbItem
	return lb, nil
}

// GetLoadBalancerCheckConf 获取负载均衡检查器
func GetLoadBalancerCheckConf(s *ServiceDetail) (load_balance.LoadBalanceChecker, error) {
	schema := "http://"
	if s.HTTPRule.NeedHttps == 1 {
		schema = "https://"
	}
	if s.Info.LoadType == utils.LoadTypeTCP || s.Info.LoadType == utils.LoadTypeGRPC {
		schema = ""
	}

	ipList := s.LoadBalance.GetIPListByModel()
	weightList := s.LoadBalance.GetWeightListByModel()
	ipWeightMap := map[string]string{}
	for i, v := range ipList {
		ipWeightMap[v] = weightList[i]
	}

	// 使用 etcd 和不用，探活方式不一样
	switch s.LoadBalance.Resolver {
	case utils.StaticConf:
		return load_balance.NewLoadBalanceCheckConf(fmt.Sprintf("%s%s", schema, "%s"), ipWeightMap)
	case utils.EtcdConf:
		return load_balance.NewLoadBalanceEtcdConf(s.Info.ServiceName, fmt.Sprintf("%s%s", schema, "%s"))
	}
	return nil, errors.New("no check conf matched")
}

var TransporterManager *Transporter

// httputil Transport 负责管理 反向代理 连接池、代理、TLS 配置等底层网络行为
type Transporter struct {
	TransportMap      map[string]*TransportItem // k = ServiceName
	TransportItemList []*TransportItem
	Locker            sync.RWMutex
}

type TransportItem struct {
	Trans       *http.Transport
	ServiceName string
}

func NewTransporter() *Transporter {
	return &Transporter{
		TransportMap:      map[string]*TransportItem{},
		TransportItemList: []*TransportItem{},
		Locker:            sync.RWMutex{},
	}
}

func init() {
	TransporterManager = NewTransporter()
}

// 每个服务一个单例， 主要是拿 DB 数据对 Transport 对象进行 连接参数配置，
// 这就是经典的 双重检查锁（Double-Checked Locking） 模式。
//先用读锁（或无锁）判断共享资源是否已初始化。
//如果未初始化，再加写锁进行初始化。
//初始化前后都要检查，防止竞态。
func (t *Transporter) GetTrans(s *ServiceDetail) (*http.Transport, error) {
	t.Locker.RLock() // 读锁查找
	if transItem, ok := t.TransportMap[s.Info.ServiceName]; ok {
		t.Locker.RUnlock()
		return transItem.Trans, nil
	}
	t.Locker.RUnlock()

	t.Locker.Lock() // 写锁写入
	defer t.Locker.Unlock()
	if transItem, ok := t.TransportMap[s.Info.ServiceName]; ok { // 再次检查 如其他协程已经写入，则找到数据返回
		return transItem.Trans, nil
	}

	//todo 优化点5
	if s.LoadBalance.UpstreamConnectTimeout == 0 {
		s.LoadBalance.UpstreamConnectTimeout = 30
	}
	if s.LoadBalance.UpstreamMaxIdle == 0 {
		s.LoadBalance.UpstreamMaxIdle = 5000
	}
	if s.LoadBalance.UpstreamIdleTimeout == 0 {
		s.LoadBalance.UpstreamIdleTimeout = 90
	}
	if s.LoadBalance.UpstreamHeaderTimeout == 0 {
		s.LoadBalance.UpstreamHeaderTimeout = 30
	}
	trans := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{Timeout: time.Duration(s.LoadBalance.UpstreamConnectTimeout) * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          0,                             // 这里的空闲连接数很影响性能
		MaxIdleConnsPerHost:   s.LoadBalance.UpstreamMaxIdle, // 每个主机的最大空闲连接数
		MaxConnsPerHost:       s.LoadBalance.UpstreamMaxIdle,
		IdleConnTimeout:       time.Duration(s.LoadBalance.UpstreamIdleTimeout) * time.Second, // 空闲连接超时时间
		TLSHandshakeTimeout:   10 * time.Second,
		ResponseHeaderTimeout: time.Duration(s.LoadBalance.UpstreamHeaderTimeout) * time.Second,
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
	}

	//save to map and slice
	transItem := &TransportItem{
		Trans:       trans,
		ServiceName: s.Info.ServiceName,
	}
	t.TransportItemList = append(t.TransportItemList, transItem)
	t.TransportMap[s.Info.ServiceName] = transItem
	return trans, nil
}
